package docbookarchivemanager.commands;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 *
 * @author Jakub Hrachovec
 */
public class CommandHandler {

    private static final ArrayList<Command> commands = new ArrayList<>();

    public static void setupCommands() {
        commands.addAll(Arrays.asList(Command.values()));
    }

    /**
     *
     * @return commands that user can use from command line.
     */
    public static ArrayList<Command> getCommands() {
        return commands;
    }

    /**
     * Parses arguments from cmd and returns used commands and attributes for
     * that command as a map.
     *
     * @param args
     * @return used commands and attributes for that command as a map.
     */
    public static HashMap<Command, ArrayList<String>> processCmdArguments(String[] args) {

        HashMap<Command, ArrayList<String>> usedCommands = new HashMap<>();
        int i = 0;
        if (args.length > 0) {
            //While an argument is a command (see Command.java) …

            outter:
            while (isCommand(args[i])) {
                Command command = getCommandByName(args[i]);
                //if command hasn't been already entered, create its entry now.
                if (!usedCommands.containsKey(command)) {
                    ArrayList<String> al = new ArrayList<>();
                    usedCommands.put(command, al);
                }
                if (args.length > i + 1) {
                    i++;
                } else {
                    break;
                }
                //If a command is followed by non-commands, add them to its entry as values.
                while (!isCommand(args[i])) {
                    //Check specific situations
                    if (command == Command.ADD_STYLESHEETS) {
                        boolean isFo = false;
                        ArrayList<String> temp = new ArrayList<>();
                        temp.add(args[i]);

                        if (args.length > i + 1) {
                            if ("-fo".equals(args[i + 1])) {
                                isFo = true;
                                i++;
                            }
                        }

                        temp.add(Boolean.toString(isFo));
                        usedCommands.get(command).addAll(temp);

                    } else if (command == Command.PARAMS) {
                        ArrayList<String> parsedParams = parseParam(args[i]);
                        if (parsedParams != null) {
                            usedCommands.get(command).addAll(parsedParams);
                        }

                        //If a value was already entered, do not add it again. 
                        //Checks only strings, so it's possible to have the same file referenced twice (e.g absolute and relative path).
                        //This is handled at a later stage.
                    } else if (!usedCommands.get(command).contains(args[i])) {
                        usedCommands.get(command).add(args[i]);
                    }

                    if (args.length > i + 1) {
                        i++;
                    } else {
                        break outter;
                    }
                }
            }
        } else {
            return null;
        }
        if (usedCommands.isEmpty()) {
            return null;
        } else {
            return usedCommands;
        }

    }

    private static ArrayList<String> parseParam(String arg) {
        String[] temp = arg.split("=", 2);
        if (temp.length == 2) {
            if ((!"".equals(temp[0])) && (!"".equals(temp[1]))) {
                return new ArrayList<>(Arrays.asList(temp));
            }
        }
        System.out.println(String.format("ERROR: Error occured while parsing %s.", arg));
        return null;

    }

    /**
     * Checks if user entered valid commands.
     *
     * @param commands
     * @return if entered commands are valid.
     * @throws IllegalArgumentException
     */
    public static boolean areCommandsValid(HashMap<Command, ArrayList<String>> commands) throws IllegalArgumentException {
        boolean temp = true;

        if (!correctNumberOfValues(commands)) {
            temp = false;
        }
        if (!containsAllRequired(commands)) {
            temp = false;
        }
        if (!containsAllDependencies(commands)) {
            temp = false;
        }
        if (!containsAtleastOneDependandcy(commands)) {
            temp = false;
        }
        if (!notContainsAnyExclusives(commands)) {
            temp = false;
        }
        return temp;

    }

    private static boolean isCommand(String command) throws IllegalArgumentException {
        for (Command comm : commands) {
            if (command.equals(comm.getCommand())) {
                return true;
            }
        }
        if ("-".equals(Character.toString(command.charAt(0)))) {
            throw new IllegalArgumentException(String.format("Unknown command: %s.", command));
        }
        return false;
    }

    private static Command getCommandByName(String n) {
        for (Command comm : commands) {
            if (n.equals(comm.getCommand())) {
                return comm;
            }
        }
        return null;

    }

    private static boolean correctNumberOfValues(HashMap<Command, ArrayList<String>> commands) {
        for (Map.Entry<Command, ArrayList<String>> entry : commands.entrySet()) {
            int minOccurs = entry.getKey().getMinOccurs();
            int maxOccurs = entry.getKey().getMaxOccurs();
            String command = entry.getKey().getCommand();
            int size = entry.getValue().size();
            if ((size < minOccurs) || (size > maxOccurs)) {
                System.out.println("ERROR: Command " + command + " requires minimum of " + minOccurs + " arguments and maximum of " + maxOccurs + " arguments.");
                return false;
            }
        }
        return true;
    }

    private static boolean containsAllRequired(HashMap<Command, ArrayList<String>> commands) {
        boolean temp = true;
        for (Command comm : getCommands()) {
            if (comm.isRequired() && !commands.containsKey(comm)) {
                System.out.println("ERROR: Command " + comm.getCommand() + " is required.");
                temp = false;
            }
        }
        return temp;
    }

    private static boolean containsAllDependencies(HashMap<Command, ArrayList<String>> commands) {
        boolean temp = true;
        for (Map.Entry<Command, ArrayList<String>> entry : commands.entrySet()) {
            if (entry.getKey().getDependencies() != null) {
                for (Command comm : entry.getKey().getDependencies()) {
                    if (!commands.containsKey(comm)) {
                        System.out.println("ERROR: Command " + entry.getKey().getCommand() + " requires command " + comm.getCommand() + ".");
                        temp = false;
                    }
                }
            }
        }
        return temp;
    }

    private static boolean containsAtleastOneDependandcy(HashMap<Command, ArrayList<String>> commands) {
        boolean temp = true;
        for (Map.Entry<Command, ArrayList<String>> entry : commands.entrySet()) {
            if (entry.getKey().getAtleastOneDependancy() != null) {
                temp = false;
                for (Command comm : entry.getKey().getAtleastOneDependancy()) {
                    if (commands.containsKey(comm)) {
                        temp = true;
                    }
                }
                if (!temp) {
                    System.out.print("ERROR: Command " + entry.getKey().getCommand() + " requires at least one of the following commands:");
                    for (Command c : entry.getKey().getAtleastOneDependancy()) {
                        System.out.print(" " + c.getCommand());
                    }
                    System.out.println(".");
                }
            }
        }
        return temp;
    }

    private static boolean notContainsAnyExclusives(HashMap<Command, ArrayList<String>> commands) {
        boolean temp = true;
        for (Map.Entry<Command, ArrayList<String>> entry : commands.entrySet()) {
            if (entry.getKey().getExclusives() != null) {
                for (Command comm : entry.getKey().getExclusives()) {
                    if (commands.containsKey(comm)) {
                        System.out.println("ERROR: Command " + entry.getKey().getCommand() + " is mutually exclusive with command " + comm.getCommand() + ".");
                        temp = false;
                    }
                }
            }
        }
        return temp;
    }

}
